{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "64c3fefd",
   "metadata": {},
   "source": [
    "# Dendritic Spine Classification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "464925da",
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset_path = \"0.025 0.025 0.1 dataset\"\n",
    "show_method = \"tsne\"\n",
    "# show_method = \"pca\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac7ecdff",
   "metadata": {},
   "source": [
    "## Load Metrics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03f63293",
   "metadata": {},
   "outputs": [],
   "source": [
    "from spine_metrics import SpineMetricDataset\n",
    "from spine_fitter import SpineGrouping\n",
    "\n",
    "\n",
    "merged_grouping = SpineGrouping().load(f\"{dataset_path}/manual_classification/manual_classification_merged_reduced.json\")\n",
    "\n",
    "# prepare the data to make the classes balanced\n",
    "min_size = min([len(value) for value in  merged_grouping.groups.values()])\n",
    "groups_rate = {key: min_size / len(value) for (key, value) in merged_grouping.groups.items()}\n",
    "merged_grouping = merged_grouping.get_balanced_subset(groups_rate)\n",
    "\n",
    "metrics = SpineMetricDataset().load(f\"{dataset_path}/metrics.csv\")\n",
    "metrics = metrics.get_spines_subset(merged_grouping.samples)\n",
    "\n",
    "classic = metrics.get_metrics_subset([\"OpenAngle\", \"CVD\", \"AverageDistance\",\n",
    "                                      \"LengthVolumeRatio\", \"LengthAreaRatio\", \"JunctionArea\",\n",
    "                                      \"Length\", \"Area\", \"Volume\", \"ConvexHullVolume\", \"ConvexHullRatio\"])\n",
    "chords = metrics.get_metrics_subset([\"OldChordDistribution\"])\n",
    "\n",
    "print(\"Merged expert classification\")\n",
    "merged_grouping.set_show_method(show_method)\n",
    "display(merged_grouping.show(metrics))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19e6b0ad",
   "metadata": {},
   "source": [
    "## SVM Classification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0583053d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn import svm\n",
    "from sklearn.model_selection import GridSearchCV\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics.pairwise import rbf_kernel, laplacian_kernel\n",
    "from sklearn.metrics import pairwise_distances\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from ipywidgets import widgets    \n",
    "import csv\n",
    "import random\n",
    "import warnings\n",
    "from notebook_widgets import create_dir\n",
    "warnings.filterwarnings(action='ignore', category=FutureWarning)\n",
    "    \n",
    "\n",
    "print(len(merged_grouping.samples))\n",
    "for (key, value) in merged_grouping.groups.items():\n",
    "    print(f\"{key}: {len(value)}\")\n",
    "\n",
    "histogram_size = 100\n",
    "random.seed(49)\n",
    "\n",
    "\n",
    "def histogram_intersection(histogram_1, histogram_2):\n",
    "    return sum([min(histogram_1[k], histogram_2[k]) for k in range(histogram_size)])\n",
    "\n",
    "\n",
    "def histogram_intersection_pairwise(X, Y):\n",
    "    return pairwise_distances(X, Y, metric=histogram_intersection)\n",
    "\n",
    "\n",
    "def combined_kernel(X, Y):\n",
    "    histograms_X = X[:, :histogram_size]\n",
    "    histograms_Y = Y[:, :histogram_size]\n",
    "    classic_X = X[:, histogram_size:]\n",
    "    classic_Y = Y[:, histogram_size:]\n",
    "    return histogram_intersection_pairwise(histograms_X, histograms_Y) + laplacian_kernel(classic_X, classic_Y)\n",
    "\n",
    "\n",
    "scores = {}\n",
    "\n",
    "\n",
    "def perform_classificaiton(label, metrics, ratios, kernel=\"rbf\", verbose=True):\n",
    "    metrics = metrics.get_spines_subset(merged_grouping.samples)\n",
    "    \n",
    "    output = []\n",
    "    pred_groupings = []\n",
    "    for train_ratio in ratios:\n",
    "        train_grouping = merged_grouping.get_balanced_subset(train_ratio)\n",
    "        train_metrics = metrics.get_spines_subset(train_grouping.samples)\n",
    "        train_names = train_metrics.ordered_spine_names\n",
    "        \n",
    "        test_grouping = merged_grouping.get_spines_subset(merged_grouping.samples.difference(train_grouping.samples))\n",
    "        test_metrics = metrics.get_spines_subset(test_grouping.samples)\n",
    "        test_names = test_metrics.ordered_spine_names\n",
    "\n",
    "        train_target = [train_grouping.get_group(name) for name in train_names]\n",
    "        test_target = [test_grouping.get_group(name) for name in test_names]\n",
    "\n",
    "        parameters = {'C': [1, 10]}\n",
    "\n",
    "        train_data = train_metrics.as_array()\n",
    "        test_data = test_metrics.as_array()\n",
    "\n",
    "        svc = svm.SVC(kernel=kernel)\n",
    "        clf = GridSearchCV(svc, parameters, cv=4)\n",
    "        clf.fit(train_data, train_target)\n",
    "        output.append(clf.score(test_data, test_target))\n",
    "        \n",
    "        pred_labels = clf.predict(metrics.as_array())\n",
    "        pred_groups = {label: set() for label in [\"Thin\", \"Mushroom\", \"Stubby\"]}\n",
    "        for i, name in enumerate(metrics.ordered_spine_names):\n",
    "            pred_groups[pred_labels[i]].add(name)\n",
    "        pred_grouping = SpineGrouping(metrics.spine_names, pred_groups)\n",
    "        pred_groupings.append(pred_grouping)\n",
    "        \n",
    "    reg = LinearRegression().fit(np.reshape(ratios, (-1, 1)), output)\n",
    "    if verbose:\n",
    "        plt.plot(ratios, output, label=label)\n",
    "        print(f\"{label} max accuracy: {max(output)}\")\n",
    "    \n",
    "    best_grouping = pred_groupings[np.argmax(output)]\n",
    "\n",
    "    scores[label] = (output)\n",
    "    \n",
    "    return best_grouping, output\n",
    "\n",
    "ratios = np.linspace(0.1, 0.9, 9)\n",
    "\n",
    "res_groupings = {}\n",
    "\n",
    "input_metrics = {\"Classic\": (classic, 'rbf'), \"Combined\": (metrics, laplacian_kernel), \"Chord\": (chords, laplacian_kernel)}\n",
    "\n",
    "for label, metric_set in input_metrics.items():\n",
    "    res_groupings[label], _ = perform_classificaiton(label, metric_set[0], ratios, metric_set[1])\n",
    "\n",
    "plt.xlabel(\"train / (train + test)\")\n",
    "plt.ylabel(\"mean accuracy\")\n",
    "plt.legend()\n",
    "\n",
    "plt.show()\n",
    "\n",
    "\n",
    "def export_graphs(_):\n",
    "    save_folder = f\"output/{dataset_path}/svm_classification\"\n",
    "    create_dir(\"output\")\n",
    "    create_dir(f\"output/{dataset_path}\")\n",
    "    create_dir(save_folder)\n",
    "\n",
    "    # save train graph\n",
    "    filename = f\"{save_folder}/training_accuracy_graphs.csv\"\n",
    "    with open(filename, mode=\"w\") as file:\n",
    "        writer = csv.writer(file)\n",
    "        writer.writerow([\"train_ratio\"] + list(ratios))\n",
    "        for label, score in scores.items():\n",
    "            writer.writerow([label] + score)\n",
    "    print(f\"Saved training accuracy graphs to '{filename}'.\")\n",
    "\n",
    "    # save groupings\n",
    "    for label, svm_grouping in res_groupings.items():\n",
    "        save_path = f\"{save_folder}/{label}\"\n",
    "        create_dir(save_path)\n",
    "        svm_grouping.set_show_method(show_method)\n",
    "        merged_grouping.set_show_method(show_method)\n",
    "        svm_grouping.save(f\"{save_path}/svm_classification.json\")\n",
    "        svm_grouping.save_reduced(input_metrics[label][0], f\"{save_path}/svm_classification_{show_method}.csv\", show_method)\n",
    "        merged_grouping.save_reduced(input_metrics[label][0], f\"{save_path}/manual_classification_{show_method}.csv\", show_method)\n",
    "        svm_grouping.save_metric_distribution(metrics, f\"{save_path}/svm_classification_metric_distribution.csv\")\n",
    "        merged_grouping.save_metric_distribution(metrics, f\"{save_path}/manual_classification_metric_distribution.csv\")\n",
    "\n",
    "        \n",
    "export_button = widgets.Button(description=\"Export to .csv\")\n",
    "export_button.on_click(export_graphs)\n",
    "display(export_button)\n",
    "\n",
    "for label, res_grouping in res_groupings.items():\n",
    "    metric_set = input_metrics[label][0]\n",
    "    res_grouping.set_show_method(show_method)\n",
    "    merged_grouping.set_show_method(show_method)\n",
    "    display(widgets.HBox([widgets.VBox([widgets.Label(\"Manual classification:\"), merged_grouping.show(metric_set)]),\n",
    "                          widgets.VBox([widgets.Label(f\"{label} metrics SVM classification:\"), res_grouping.show(metric_set)])]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f00d0a47",
   "metadata": {},
   "source": [
    "## 2 Datasets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9b69839",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn import svm\n",
    "from sklearn.model_selection import GridSearchCV\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics.pairwise import rbf_kernel\n",
    "from sklearn.metrics import pairwise_distances\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from typing import Tuple    \n",
    "from spine_metrics import SpineMetricDataset\n",
    "from spine_fitter import SpineGrouping\n",
    "from ipywidgets import widgets\n",
    "\n",
    "    \n",
    "# [Merged Grouping, Combined Metrics, Classic metrics, Chord metrics]\n",
    "def load_dataset(dataset_path) -> Tuple[SpineGrouping, SpineMetricDataset, SpineMetricDataset, SpineMetricDataset]:\n",
    "    grouping = SpineGrouping().load(f\"{dataset_path}/manual_classification/manual_classification_merged_reduced.json\")\n",
    "\n",
    "    metrics = SpineMetricDataset().load(f\"{dataset_path}/metrics.csv\")\n",
    "    metrics = metrics.get_spines_subset(grouping.samples)\n",
    "\n",
    "    classic = metrics.get_metrics_subset([\"OpenAngle\", \"CVD\", \"AverageDistance\",\n",
    "                                          \"LengthVolumeRatio\", \"LengthAreaRatio\", \"JunctionArea\",\n",
    "                                          \"Length\", \"Area\", \"Volume\", \"ConvexHullVolume\", \"ConvexHullRatio\"])\n",
    "    chords = metrics.get_metrics_subset([\"OldChordDistribution\"])\n",
    "\n",
    "    return grouping, metrics, classic, chords\n",
    "\n",
    "\n",
    "train = load_dataset(\"train 0.025 0.025 0.1 dataset\")\n",
    "test = load_dataset(\"test 0.025 0.025 0.1 dataset\")\n",
    "union = load_dataset(\"0.025 0.025 0.1 dataset\")\n",
    "\n",
    "train, test = test, train\n",
    "\n",
    "\n",
    "histogram_size = 100\n",
    "\n",
    "def do_classification(label, train_grouping, train_metrics, test_grouping, test_metrics):\n",
    "    pred_groupings = []\n",
    "\n",
    "    train_names = train_metrics.ordered_spine_names\n",
    "    test_names = test_metrics.ordered_spine_names\n",
    "\n",
    "    train_target = [train_grouping.get_group(name) for name in train_names]\n",
    "    test_target = [test_grouping.get_group(name) for name in test_names]\n",
    "\n",
    "    parameters = {'C': [1, 10]}\n",
    "\n",
    "    train_data = train_metrics.as_array()\n",
    "    test_data = test_metrics.as_array()\n",
    "\n",
    "    svc = svm.SVC(kernel=\"rbf\")\n",
    "    clf = GridSearchCV(svc, parameters, cv=4)\n",
    "    clf.fit(train_data, train_target)\n",
    "    print(f\"{label} metrics SVM accuracy = {clf.score(test_data, test_target)}\")\n",
    "\n",
    "    pred_labels = clf.predict(test_metrics.as_array())\n",
    "    pred_groups = {label: set() for label in [\"Thin\", \"Mushroom\", \"Stubby\"]}\n",
    "    for i, name in enumerate(test_metrics.ordered_spine_names):\n",
    "        pred_groups[pred_labels[i]].add(name)\n",
    "    pred_grouping = SpineGrouping(test_metrics.spine_names, pred_groups)\n",
    "    test_grouping.set_show_method(show_method)\n",
    "    pred_grouping.set_show_method(show_method)\n",
    "    display(widgets.HBox([test_grouping.show(test_metrics), pred_grouping.show(test_metrics)]))\n",
    "    return pred_grouping\n",
    "    \n",
    "    \n",
    "res_groupings = {}\n",
    "test_metrics = {\"Classic\": test[2], \"Combined\": test[1], \"Chord\": test[3]}\n",
    "\n",
    "\n",
    "def export_graphs(_):\n",
    "    save_folder = f\"output/train_and_test_separately\"\n",
    "    create_dir(\"output\")\n",
    "    create_dir(save_folder)\n",
    "\n",
    "    # save groupings\n",
    "    for label, svm_grouping in res_groupings.items():\n",
    "        save_path = f\"{save_folder}/{label}\"\n",
    "        create_dir(save_path)\n",
    "        svm_grouping.set_show_method(show_method)\n",
    "        test[0].set_show_method(show_method)\n",
    "        svm_grouping.save(f\"{save_path}/svm_classification.json\")\n",
    "        svm_grouping.save_reduced(test_metrics[label], f\"{save_path}/svm_classification_tsne.csv\", \"tsne\")\n",
    "        test[0].save_reduced(test_metrics[label], f\"{save_path}/manual_classification_tsne.csv\", \"tsne\")\n",
    "        svm_grouping.save_metric_distribution(test_metrics[\"Combined\"], f\"{save_path}/svm_classification_metric_distribution.csv\")\n",
    "        test[0].save_metric_distribution(test_metrics[\"Combined\"], f\"{save_path}/manual_classification_metric_distribution.csv\")\n",
    "    print(f'Saved to \"{save_folder}\"')\n",
    "\n",
    "        \n",
    "export_button = widgets.Button(description=\"Export to .csv\")\n",
    "export_button.on_click(export_graphs)\n",
    "display(export_button)\n",
    "\n",
    "res_groupings[\"Classic\"] = do_classification(\"Classic\", train[0], train[2], test[0], test[2])\n",
    "res_groupings[\"Combined\"] = do_classification(\"Combined\", train[0], train[1], test[0], test[1])\n",
    "res_groupings[\"Chord\"] = do_classification(\"Chord\", train[0], train[3], test[0], test[3])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b01cc1b1",
   "metadata": {},
   "source": [
    "## Expert Accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ed90e5e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "path = Path(f\"{dataset_path}/manual_classification\")\n",
    "classifications_paths = path.glob(\"manual_classification_?.json\")\n",
    "groupings = [SpineGrouping().load(str(path)) for path in classifications_paths]\n",
    "\n",
    "\n",
    "for i, grouping in enumerate(groupings):\n",
    "    print(f\"{i + 1}: {SpineGrouping.accuracy(merged_grouping, grouping):.2f}\")\n",
    "print(f\"Mean: {np.mean([SpineGrouping.accuracy(merged_grouping, grouping) for grouping in groupings]):.2f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
